Bearer トークンについて
概要
Bearer トークンは、OAuth 2.0 の保護リソースへアクセスするために署名無しトークンを HTTP リクエスト中でどのように利用するか記述したもの。Bearer トークンを所有しているユーザーは関連付けられたリソースへアクセスするために暗号鍵等で署名の検証を行う必要はない。したがって、Bearer トークンは保存場所やリクエストから漏れることを防ぐ必要がある。
Bearer トークンは認可サーバーからクライアントに対して発行され、クライアントはリソースサーバーが持つ保護されたリソースに対してアクセスするために使用する。
ちなみに、Bearer は日本語で「担い手」という意味。トークンを所有している任意の Bearer はこのトークンを使用できることから Bearer トークンと名付けられた。
認証されたリクエスト
Bearer トークンをリソースサーバーに送信する方法は3つ定義されている。結論を先に述べると、基本的には1番に記載している Authorization リクエストヘッダフィールド の方式を採用するのがセキュリティ的な観点から最もベター。
Authorization リクエストヘッダフィールド
リクエストヘッダに Bearer トークンを載せる方式
form エンコードされたボディパラメータ
HTTP リクエスト内に Bearer トークンを含ませる場合は、クライアントは access_token パラメータを用いてアクセストークンをリクエストボディに付加する。クライアントは以下の条件のすべてを必ず満たしている必要がある。満たしていない場合は使用してはならない。
HTTP リクエストのエンティティヘッダに Content-Type フィールドが application/x-www-form-urlencoded となっている
エンティティボディは application/x-www-form-urlencoded 要求に従う
HTTP リクエストのエンティティボディはシングルパートである
エンティティボディ中にエンコードされたコンテンツはすべて ASCII 文字列だけで構成されている
GET メソッドを使用してはいけない
この方法は関与しているブラウザが Authorization リクエストヘッダにアクセスできない場合を除いて基本的には使用するべきではない。
URI クエリパラメータ
HTTP リクエスト URI の中でアクセストークンを送信する際はクライアントは access_token をクエリパラメータに含めてリクエストを送信することができる。
クライアントは URI クエリパラメータ方式を利用して、no-store オプションを含んだ Cache-Control ヘッダーを送信すべきである。
URL 中のアクセストークンがログに記録される可能性が高いことなど、この方式を採用した場合はセキュリティレベルが下がってしまうことからアクセストークンを Authorization リクエストヘッダもしくは HTTP リクエストエンティティボディ中で送信することが不可能でない限りは使用するべきではない。
WWW-Authenticate レスポンスヘッダフィールド
realm 属性について
保護リソースの保護の範囲を示すために realm 属性を用いても良い。
scope 属性について
scope 属性は大文字・小文字を区別するスペース区切りのリストであり、要求されたリソースへのアクセスに必要なアクセストークンのスコープを示す。scope 属性の区切り文字はスペース以外は許容されない。 scope の値を一元的に管理する組織は存在せず、許容される値は認可サーバーで定義することができる。
scope の値の例は以下の通り。
code:scope-example
scope="openid profile email"
scope="urn:example:channel=HBO&urn:example:rating=G,PG-13"
エラーについて
保護リソースへのリクエストにアクセストークンが含まれているが認証に失敗した場合、error 属性を含むべきである。より詳しいエラー失敗の原因について説明するため、error_description 属性を含んでも良い。これらの error や error_description はエンドユーザーに表示されることを想定されていない。エラーについての詳しい説明のためのページを指し示す、error_uri 属性を含んでも良い。
エラーコード
典型的なエラーコードとその原因についてまとめる。
invalid_request
原因
リクエストに必要なパラメータが不足している
対応していないパラメータもしくはパラメータの値が含まれている
同じパラメータが複数回現れている
1つのアクセストークンを含むために複数の方法を用いている
リクエストがその他不正な形式になっている
ステータスコード:400 (Bad Request)
invalid_token
原因
提供されたアクセストークンが期限切れである
revoke されている
不正な値である
その他の理由により無効である
ステータスコード:401 (Unauthorized)
クライアントは新しいアクセストークンを要求して保護されたリソースへのアクセスを再試行しても良い
insufficient_scope
原因
リクエストにはアクセストークンにより提供されるよりも高い権限が必要である
ステータスコード:403 (Forbidden)
保護されたリソースへのアクセスに必要となるスコープを示した scope 属性を含めてもよい
リクエストが一切の認証情報を含まない場合は、リソースサーバーはエラーコードやその他のエラー情報を応答に含めるべきではない。
セキュリティ
Bearer トークン利用時におけるトークンの取り扱い方法に関するセキュリティ上の脅威、及びそれらの脅威をどのように軽減するかを記述する
セキュリティ上の脅威
トークンの偽装・改ざん
トークンの露見
トークンリダイレクト
攻撃者はあるリソースサーバーで利用することを目的として生成されたトークンを別のリソースサーバーに対して利用し、後者が持つリソースにアクセスを試みる
トークンリプレイ
攻撃者は過去にリソースサーバーで利用済みのトークンの再利用を試みる
脅威の軽減
デジタル署名やメッセージ認証コード(MAC)を用いてトークンの内容を保護することにより多くの脅威は軽減できる。あるいは、Bearer トークンはエンコードされた認可情報を直接含むのではなく、認可情報に対する参照を含むこともできる。その参照は攻撃者が推測不可能である必要がある。
トークンリダイレクトへの対策としては、トークン内に認可サーバーの意図する一般的に1つのリソースサーバーまたはリソースサーバーからなるトークンの受領者(audience)のアイディンティティを含むことが重要である。トークンの利用できる範囲を特定の範囲に制限することも推奨される。
クッキーは一般的に平文で送信されるためクッキーに含まれる情報は露呈する危険性を持つ。そのため、クッキーを平文で送信できる場合 Bearer トークンをクッキー内に格納しては行けない。
トークンの覗き見とトークンリプレイへの対策として、トークンの有効期限が制限されている必要がある。これを実現する1つの方法として、トークン内の保護された部分に有効な時間のフィールドを保持しておくことである。1時間以下のトークンを利用することによりトークンが露呈した場合の影響が軽減されることに留意すべき。
推奨事項まとめ
意図していない相手に Bearer トークンが漏れていないということを確実にする
TLS 証明書チェインを検証する
常に TLS を使用する
クッキーに Bearer トークンを格納しない
トークンの有効期限は1時間以内と短命にする
スコープが設定された Bearer トークンを発行する
Bearer トークンをページ URL 中で渡さず、ヘッダーやボディの中で渡されるべきである
Bearer トークンがページ URL として渡された場合、攻撃者は履歴情報やログ、その他の保護されていない場所から盗み取ることができるかもしれない
気になったこと
「トークン内の保護された部分」というのはどのようにすれば「保護された」となるのだろうか?
思い浮かんだのだと、トークンを管理するテーブルに有効期限のカラムを設定するというくらい?
トークンのどこかに保管していると有効期限の偽装が行われそうな気がしている
トークン内のフィールドが偽装されていないことは署名すればよいが、それだと Bearer ではなくなるよな?
Bearer トークンの権限についても DB でリスト方式で持つでよいのかな?
ただリクエストごとに DB への SELECT が走るのはパフォーマンス的に辛いものではないだろうか?
redis とかに持たせるのが良いのかな?